package org.biogroovy.io.eutils;

import java.text.DateFormat
import java.text.SimpleDateFormat

import javax.xml.namespace.QName
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.xpath.*

import org.biogroovy.conf.BioGroovyConfig
import org.biogroovy.eutils.EUtilsURLFactory
import org.biogroovy.io.AbsXmlReader
import org.biogroovy.io.IFetcher;
import org.biogroovy.models.*
import org.w3c.dom.Node

/**
 * Reads an NCBI EUtils XML response
 */
class OmimReader extends AbsXmlReader<Omim> {
	
	private static final String DATABASE_NAME = "omim";

	/**
	 * A map of XPath expressions.  The keys are fields in the Omim class, and the values
	 * are XPath expressions.
	 */
	static final Map<String, String> XPATH_MAP = [
		mimId:'/Mim-entries/Mim-entry/Mim-entry_mimNumber',
		title:'/Mim-entries/Mim-entry/Mim-entry_title',
		symbol:'/Mim-entries/Mim-entry/Mim-entry_symbol',
		locus:'/Mim-entries/Mim-entry/Mim-entry_locus',
		textElement:'/Mim-entries/Mim-entry/Mim-entry_text/Mim-text',
		textFields:'/Mim-entries/Mim-entry/Mim-entry_textfields/Mim-text',
		aliases:'/Mim-entries/Mim-entry/Mim-entry_aliases/Mim-entry_aliases_E',

		medlineLinks:'/Mim-entries/Mim-entry/Mim-entry_medlineLinks/Mim-link/Mim-link_uids',
		proteinLinks:'/Mim-entries/Mim-entry/Mim-entry_proteinLinks/Mim-link/Mim-link_uids',
		nucleotideLinks:'/Mim-entries/Mim-entry/Mim-entry_nucleotideLink/Mim-link/Mim-link_uids',
		clinicalSynopsis:'/Mim-entries/Mim-entry/Mim-entry_clinicalSynopsis/Mim-index-term',
		references:'/Mim-entries/Mim-entry/Mim-entry_references/Mim-reference'

	];

	/**
	 * A map of node types.  The keys are the fields in the Omim class, and the values are 
	 * the node types.
	 */
	static final Map<String, QName> NODE_TYPE_MAP = [
		mimId:XPathConstants.STRING,
		title:XPathConstants.STRING,
		symbol:XPathConstants.STRING,
		locus:XPathConstants.STRING,
		aliases:XPathConstants.NODESET

	]

	static final String ROOT_PATH = "//Mim-entries/Mim-entry";
	
	String tool = null;
	String email = null;
	
	/**
	 * Constructor.
	 */
	public OmimReader(){
		this.databaseName = DATABASE_NAME;
		ConfigObject conf = BioGroovyConfig.getConfig();
		this.tool = conf.eutils.tool
		this.email = conf.eutils.email
	}

	/**
	 * This method reads a single OMIM record from EUtils.
	 * @param id The OMIM record ID.
	 */
	public Omim read(String id) {
		URL url = getUrl(id, [tool:this.tool, email:this.email]);
		return read(url.openStream());
	}

	/**
	 * This method reads an OMIM record from the input stream.
	 * @param is  The input stream containing the OMIM record.
	 * @return An OMIM object.
	 */
	public Omim read(InputStream is) {
		Omim omim = new Omim();
		def builder  = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		Node root     = (Node)builder.parse(is).documentElement
		parse(omim, root)
		
		builder = null;
		root = null;

		return omim
	}

	/**
	 * This method reads an OMIM record from a file
	 * @param file The XML record.
	 * @return An OMIM record.
	 */
	public Omim read(File file) {
		if (!file.exists()){
			throw new FileNotFoundException("The file was not found: " + file.absolutePath)
		}
		return read(new FileInputStream(file));
	}

	/**
	 * This method parses text elements, and populates an Omim object with the results.
	 * 
	 * @param root	The root node
	 * @param omim	The Omim object.
	 */
	private void parseTextElements(def root, Omim omim){
		def xpath = XPathFactory.newInstance().newXPath();
		def textElementNodes = xpath.evaluate(XPATH_MAP.textFields, root, XPathConstants.NODESET);

		textElementNodes.each{ parseTextElement(it, omim) }

		xpath = XPathFactory.newInstance().newXPath();
		textElementNodes = xpath.evaluate(XPATH_MAP.textElement, root, XPathConstants.NODESET);
		textElementNodes.each{ parseTextElement(it, omim) }
	}

	/**
	 * This method parses a single text element.
	 * 
	 * @param root	The root element
	 * @param omim	The Omim object to be populated.
	 */
	private void parseTextElement(def root, Omim omim){
		OmimText omimText = new OmimText();

		def xpath = XPathFactory.newInstance().newXPath();
		omimText.type = xpath.evaluate("./Mim-text_label", root, XPathConstants.STRING);

		xpath = XPathFactory.newInstance().newXPath();
		omimText.text = xpath.evaluate("./Mim-text_text", root, XPathConstants.STRING);

		xpath = XPathFactory.newInstance().newXPath();
		omimText.uids = xpath.evaluate("./Mim-text_neighbors/Mim-link/Mim-link_uids", root, XPathConstants.STRING);

		omim.addOmimText( omimText);
	}

	/**
	 * This method parses the links and populates the Omim object.
	 * @param root	The root element.
	 * @param omim	The Omim object to be populated.
	 * @param xpathKey	A key to the xpath map
	 */
	private void parseLinks(def root, Omim omim, String xpathKey){
		def xpath = XPathFactory.newInstance().newXPath();
		String temp = xpath.evaluate(XPATH_MAP.get(xpathKey), root, XPathConstants.STRING);
		if (temp != null){
			omim.setProperty(xpathKey, Arrays.asList(temp.split(",")));
		}
	}

	/**
	 * This method parses clinical synopses and populates the Omim object.
	 * @param root	The root element.
	 * @param omim	The Omim object to be populated.
	 */
	private void parseClinicalSynopsis(def root, Omim omim){
		def xpath = XPathFactory.newInstance().newXPath();
		def clinSynNodes = xpath.evaluate(XPATH_MAP.clinicalSynopsis, root, XPathConstants.NODESET);

		def keyNodeXPath = XPathFactory.newInstance().newXPath();
		def termXPath = XPathFactory.newInstance().newXPath();

		clinSynNodes.each{clinSynNode ->
			def keyNode = keyNodeXPath.evaluate("./Mim-index-term_key", clinSynNode, XPathConstants.STRING)
			def termNodes = termXPath.evaluate("./Mim-index-term_terms/Mim-index-term_terms_E",clinSynNode, XPathConstants.NODESET)

			termNodes.each{
				omim.clinicalSynopsis.put (keyNode, it)
			}
		}
	}

	/**
	 * This method parses references and populates an Omim object.
	 * @param root	The root element.
	 * @param omim	The Omim object to be populated.
	 */
	private void parseReferences(def root, Omim omim){
		def xpath = XPathFactory.newInstance().newXPath();
		def refNodes = xpath.evaluate(XPATH_MAP.references, root, XPathConstants.NODESET);

		def authorXPath = XPathFactory.newInstance().newXPath();
		def titleXPath = XPathFactory.newInstance().newXPath();
		def volXPath = XPathFactory.newInstance().newXPath();
		def journalXPath = XPathFactory.newInstance().newXPath();
		def pmidXPath = XPathFactory.newInstance().newXPath();
		def yearXPath = XPathFactory.newInstance().newXPath();
		def monthXPath = XPathFactory.newInstance().newXPath();
		def dayXPath = XPathFactory.newInstance().newXPath();

		DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");

		refNodes.each{refNode->
			Article art = new Article();
			art.title = titleXPath.evaluate("./Mim-reference_citationTitle", root, XPathConstants.STRING);
			art.pubmedId = pmidXPath.evaluate("./Mim-reference_pubmedUID", root, XPathConstants.STRING);
			art.journal.title = journalXPath.evaluate("./Mim-reference_journal", root, XPathConstants.STRING);

			String year = yearXPath.evaluate("./Mim-reference_pubDate/Mim-date/Mim-date_year", root, XPathConstants.STRING);
			String month = monthXPath.evaluate("./Mim-reference_pubDate/Mim-date/Mim-date_month", root, XPathConstants.STRING);
			String day = dayXPath.evaluate("./Mim-reference_pubDate/Mim-date/Mim-date_day", root, XPathConstants.STRING);
			day = (day == "0")?"1":day;

			if (year == "" || month == "" || day == ""){
				art.journal.publicationDate = new Date();
			}else {
				art.journal.publicationDate = dateFormat.parse("${year}/${month}/${day}");
			}

			def authors = authorXPath.evaluate("./Mim-reference_authors/Mim-author/Mim-author_name", root, XPathConstants.STRING);

			authors.each{author ->
				Author auth = new Author();
				String[] names = author.split(",");
				auth.lastname = names[0].trim();
				auth.firstname = names[1].trim();
				art.authors.add(auth);
			}

			omim.references.add(art);
		}
	}
    
    
    public List<Omim> readList(String idList) throws IOException{
		URL url = getUrl(idList, [tool:this.tool, email:this.email]);
        return readList(url.openStream());
    }

	/**
	 * This method reads a list of OMIM objects from 
	 * @param inputStream  An input stream containing the XML record.
	 * @return a list of OMIM objects
	 */
	public List<Omim> readList(InputStream inputStream) throws IOException{
		List<Omim> mimList = new ArrayList<Omim>();

		def builder  = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		def root     = builder.parse(inputStream).documentElement

		XPath xpath = XPathFactory.newInstance().newXPath();
		NodeList nodeList = xpath.evaluate(ROOT_PATH, root, XPathConstants.NODESET);
		for(int i=0; i < nodeList.getLength(); i++){
			Node node = nodeList.item(i)
			Omim mim = new Omim();
			parseData(node, mim, XPATH_MAP, NODE_TYPE_MAP);
			mimList.add(mim);
		}

		builder = null;
		root = null;

		return mimList;
	}

	@Override
	public void parse(Omim omim, Node root) {
		parseData(root, omim, XPATH_MAP, NODE_TYPE_MAP);
		parseTextElements(root, omim)
		parseClinicalSynopsis(root, omim)
		parseLinks(root, omim, "medlineLinks")
		parseLinks(root, omim, "proteinLinks")
		parseLinks(root, omim, "nucleotideLinks")
		parseReferences(root, omim)
	}

	@Override
	public Omim fetch(String id) throws IOException {
		URL url = getUrl(id, [tool:this.tool, email:this.email]);
		return read(url.openStream())
	}

	@Override
	public URL getUrl(String id, Map<String, String> paramMap) {
		Map<String, String> map = [db:EUtilsURLFactory.DB_OMIM,id:id, retmode:'xml']
		map.putAll(paramMap)
		
		String url = EUtilsURLFactory.getURL(EUtilsURLFactory.EFETCH, map)
		return new URL(url);
	}


	@Override
	public List<Omim> fetchAll(String id) throws IOException {
		URL url = getUrl(id, [tool:this.tool, email:this.email]);
		return readList(url.openStream())
	}

	@Override
	public IFetcher<Omim> getNewInstance() {
		return new OmimReader();
	}
}
